/**
  ******************************************************************************
  * @file    udp_client.h
  * @author  Alex Liu 
  * @version V1.0.1
  * @date    2020/08/13
  * @brief   sample udp library tib_k331
  ******************************************************************************
  * @attention
  *
  ******************************************************************************
  */
#ifndef UDP_CLIENT_H
#define UDP_CLIENT_H

#include <iostream>
#include <string>
#include <memory>
#include <mutex>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>

using namespace std;
using namespace boost::asio;

class UdpHandler {
 public:
  static string dst_ip_addr;  // target
  static string dst_ip_addr2;
  static string src_ip_addr;  // local

  static unsigned short dst_port;
  static unsigned short dst_port2;
  static unsigned short src_port;



 public:
  explicit UdpHandler(size_t buffer_length) {
    udp_socket = std::make_unique<ip::udp::socket>(ios);
    dst_addr = new ip::udp::endpoint(ip::address::from_string(dst_ip_addr), 
                                     dst_port);
    dst_addr2 = new ip::udp::endpoint(ip::address::from_string(dst_ip_addr2), 
                                     dst_port2);                                     
    src_addr = new ip::udp::endpoint(ip::address::from_string(src_ip_addr),
                                     src_port);
    udp_socket->open(dst_addr->protocol());
    // udp_socket->open(dst_addr2->protocol());    
    udp_socket->bind(*src_addr);

    send_buf_ = new unsigned char[buffer_length]();
    recv_buf_ = new unsigned char[buffer_length]();
    buffer_length_ = buffer_length;
    frame_length_ = 0;
    exit_flag_ = false;//这个没用上，但这个的存在提高了udp的便利性
    send_flag_ = false;

    udp_recv_thread_ = std::thread(&UdpHandler::UdpReceive, this);
    udp_send_thread_ = std::thread(&UdpHandler::UdpTransmitFlush, this);
  }

  ~UdpHandler() {
    udp_socket->close();
    if (udp_recv_thread_.joinable()) udp_recv_thread_.join();
    if (udp_send_thread_.joinable()) udp_send_thread_.join();
    delete [] send_buf_;
    delete [] recv_buf_;
    delete dst_addr;
    delete src_addr;
  }
  
  virtual void UdpTransmit() {}

  void UdpTransmitFlush() {
    while (!exit_flag_) {
      if (!send_flag_) continue;
      std::unique_lock<std::mutex> lock(send_mutex_);
      udp_socket->send_to(buffer(send_buf_, frame_length_), *dst_addr, 0, 
                                 ignore_error);
      udp_socket->send_to(buffer(send_buf_, frame_length_), *dst_addr2, 0, 
                                 ignore_error);//nuc地址，
      std::cout << "udp msg sent!" << std::endl;
      send_flag_ = false;
    }
  }

  void UdpReceive() {
    boost::system::error_code ec;
    std::vector<unsigned char> frame_buf;
    udp_socket->async_receive_from(
        buffer(recv_buf_, buffer_length_),
        *dst_addr,
        boost::bind(&UdpHandler::RecvHandler, this, frame_buf, _1, _2));
    ios.run();
    std::cout << "exit~" << std::endl;
  }

 protected:
  std::vector<unsigned char> recv_frame_;
  std::mutex recv_mutex_;
  std::mutex send_mutex_;
  unsigned char *send_buf_;
  size_t frame_length_;
  volatile bool send_flag_;
  volatile bool exit_flag_;

 private:
  enum FrameState {
    kIdle = 0,
    kHeaderCaught,
    kHeaderChecked,
  };

 private:
  void RecvHandler(std::vector<unsigned char> &buf, 
                   boost::system::error_code ec,
                   size_t bytes_transferred) {
    if (!ec) {
      for (size_t i = 0; i < bytes_transferred; i++) {
        switch (frame_state_) {
          case kIdle:
            if (recv_buf_[i] == 0x55) {
              frame_state_ = kHeaderCaught;
            }
            break;
          case kHeaderCaught:
            if (recv_buf_[i] == 0xaa) {
              frame_state_ = kHeaderChecked;
            }
            break;
          case kHeaderChecked:
            if (buf.size() && buf.size() == buf.front() + 1) {
              buf.erase(buf.begin());
              recv_mutex_.lock();
              recv_frame_ = buf;  // deep copy
              recv_mutex_.unlock();
              buf.clear();
              frame_state_ = kIdle;
            }
            else {
              buf.push_back(recv_buf_[i]);
            }
            break;
          default: break;
        }
      }
    }
    memset(recv_buf_, 0, buffer_length_);
    if (exit_flag_) return;
    udp_socket->async_receive_from(
        buffer(recv_buf_, buffer_length_),
        *dst_addr,
        boost::bind(&UdpHandler::RecvHandler, this, buf, _1, _2));
  }

  io_service ios;
  std::unique_ptr<ip::udp::socket> udp_socket;
  ip::udp::endpoint *dst_addr, *src_addr, *dst_addr2;
  boost::system::error_code ignore_error;
  
  unsigned char *recv_buf_;
  size_t buffer_length_;
  FrameState frame_state_;
  
  std::thread udp_recv_thread_;
  std::thread udp_send_thread_;
};

#endif  // UDP_CLIENT_H